package api;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("rawtypes")
public class ControlReflection {

	public ControlReflection(){}
	
	//Devuelve un listado de todas las clases del paquete con el nombre pasado por parametro
	//Si no hay clases en ese paquete devuelve una lista vacia
	public List<Class> getClases(String nombrePaquete){
		List<Class> clases = new ArrayList<Class>();
		
		File directorio = new File("bin/"+nombrePaquete);
	    File[] archivos = directorio.listFiles();
	    
	    if (archivos != null) {
	    	for (int i = 0; i < archivos.length; i++){
	    		File archivo = archivos[i];
	    		String nombre = archivo.getName();
	    		if (archivo.isFile() && nombre.endsWith(".class")){
	    			nombre = nombrePaquete + "." + nombre.substring(0, nombre.length()-6);
	    			Class clase = null;
		    		try {
						clase = Class.forName(nombre);
					} catch (ClassNotFoundException e) {
						//TODO definir alguna excepcion propia
						System.out.println("falta alguna clase necesaria para " + nombre);
						//e.printStackTrace();
					}
					if (clase != null) clases.add(clase);
		    	}
		    }
	    }
		return clases;
	}
	
	//Devuelve un listado de los metodos publicos declarados en la clase que se pasa por parametro
	//Si no hay metodos para devolver, devuelve una lista vacia
	//No devuelve el metodo "notificar" que la clase tiene por implementar la interfaz notificador
	public List<Metodo> getMetodosPropios(Class clase,boolean todos){
		List<Metodo> metodosReturn = new ArrayList<Metodo>();
		Method[] metodos = clase.getDeclaredMethods();
		
		//recorro el arreglo de metodos
		for (int i = 0; i < metodos.length; i++){
			Method metodo = metodos[i];
			//si el metodo es de la clase y es publico lo agrego al ArrayList
			//TODO revisar si esta bien lo de filtrar el metodo "notificar"
			if (todos || (metodo.getModifiers() == Modifier.PUBLIC
					&& metodo.getName().compareTo("notificar") != 0
					&& metodo.getName().compareTo("registrarObservador") != 0
					&& metodo.getName().compareTo("toString") != 0)
					&& !metodo.getName().startsWith("get")
					&& !metodo.getName().startsWith("set")){
				Metodo m = new Metodo(metodo);
				metodosReturn.add(m);
			}
		}
		return metodosReturn;
	}
	
	public Metodo getMetodo(Class clase,String nombre){
		Metodo metodoReturn = null;
		Method[] metodos = clase.getDeclaredMethods();
		
		boolean encontrado = false;
		int i = 0;
		
		while (!encontrado && i < metodos.length){
			Method metodo = metodos[i];
			if (nombre.compareTo(metodo.getName()) == 0){
				metodoReturn = new Metodo(metodo);
				encontrado = true;
			} else i++;
		}

		return metodoReturn;
	}
	
	//Devuelve un listado de los atributos declarados en la clase que se pasa por parametro
	//Si no hay atributos para devolver, una lista vacia
	//Si "todos" es true, devuelve todos los atributos de la clase, si "todos" es false NO devuelve:
	//-los sucesos que puede notificar un objeto
	//-el atributo "observador"
	public List<Atributo> getAtributosPropios(Class clase,boolean todos){
		List<Atributo> atributosReturn = new ArrayList<Atributo>();
		Field[] atributos = clase.getDeclaredFields();
		for (int i = 0; i < atributos.length; i++){
			Field f = atributos[i];
			Atributo atributo = new Atributo(f);
			if( todos || (atributo.getName().compareTo("observador")!=0 && !f.toString().startsWith("public static")))
				atributosReturn.add(atributo);
		}
		return atributosReturn;
	}
	
	//Devuelve un listado de los sucesos que puede notificar un objeto de la clase que se pasa por parametro
	//Si no hay sucesos para devolver, una lista vacia
	//No devuelve el atributo "abservador" que la clase tiene que tener como requisito de la api
	public List<Suceso> getSucesosPosibles(Class clase){
		List<Suceso> sucesosReturn = new ArrayList<Suceso>();

		Field[] atributos = clase.getDeclaredFields();
		for (int i = 0; i < atributos.length; i++){
			Field f = atributos[i];
			if (f.toString().startsWith("public static java.lang.String")){
				try {
					sucesosReturn.add(new Suceso(f.get(null).toString()));
				} catch (IllegalArgumentException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		return sucesosReturn;
	}
	
	//Ejecuta el "metodo" de "objeto" con los "argumentos" pasados por parametro, si no hay argumentos usar el otro ejecutar
	public Object ejecutar(Object objeto, Metodo metodo, ArrayList<Object> argumentos){
		Object valorReturn = null;
		if (objeto != null && metodo != null){
			try {
				if (argumentos == null) valorReturn = metodo.getMetodo().invoke(objeto);
				else valorReturn = metodo.getMetodo().invoke(objeto, argumentos.toArray());
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			//TODO excepcion o devuelve null?
		}
		return valorReturn;
	}
	
	public Object ejecutar(Object objeto, Metodo metodo){
		return this.ejecutar(objeto, metodo, null);
	}
	
	//Devuelve el valor del "atributo" de "objeto"
	public Object obtenerValor(Object objeto,Atributo atributo){
		atributo.getAtributo().setAccessible(true);
		Object valorReturn = null;
		if (objeto != null && atributo != null){
			try {
				valorReturn = atributo.getAtributo().get(objeto);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			//TODO excepcion o devuelve null?
		}
		atributo.getAtributo().setAccessible(false);
		return valorReturn;
	}
	
	//Cambia el valor de "atributo" de "objeto" por "valor"
	public void cambiarValor(Object objeto,Atributo atributo,Object valor){
		if ((objeto != null && atributo != null && valor != null)
				&& (getAtributosPropios(objeto.getClass(),true).contains(atributo))){
			atributo.getAtributo().setAccessible(true);
			try {
				atributo.getAtributo().set(objeto,valor);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			//TODO excepcion o devuelve null?
		}
		atributo.getAtributo().setAccessible(false);
	}
	
	//Devuelve true si los dos objetos son iguales, devuelve false si son distintos
	public Boolean compararIgualdad(Object obj1, Object obj2){
		Boolean valorReturn = null;
		if (obj1 != null && obj2 != null) {
			Class clase = obj1.getClass();
			if (clase.equals(obj2.getClass())){
				//TODO comparar con mas tipos comunes
				if (clase.getSimpleName().compareTo("boolean") == 0 || clase.getSimpleName().compareTo("Boolean") == 0){
					valorReturn = new Boolean(((Boolean)obj1 == (Boolean)obj2));
				} else if (clase.getSimpleName().compareTo("int") == 0 || clase.getSimpleName().compareTo("Integer") == 0){
					valorReturn = new Boolean(((Integer)obj1 == (Integer)obj2));
				} else if (clase.getSimpleName().compareTo("float") == 0 || clase.getSimpleName().compareTo("Float") == 0){
					valorReturn = new Boolean(((Float)obj1 == (Float)obj2));
				} else if (clase.getSimpleName().compareTo("double") == 0 || clase.getSimpleName().compareTo("Double") == 0){
					valorReturn = new Boolean(((Double)obj1 == (Double)obj2));
				} else if (clase.getSimpleName().compareTo("long") == 0 || clase.getSimpleName().compareTo("Long") == 0){
					valorReturn = new Boolean(((Long)obj1 == (Long)obj2));
				} else if (clase.getSimpleName().compareTo("char") == 0 || clase.getSimpleName().compareTo("Character") == 0){
					valorReturn = new Boolean(((Character)obj1 == (Character)obj2));
				} else if (clase.getSimpleName().compareTo("String") == 0){
					valorReturn = new Boolean((((String)obj1).compareTo((String)obj2) == 0));
				} else {
					valorReturn = new Boolean(obj1.equals(obj2));
				}
			} else {
				//TODO lanzar excepcion comparandoObjetosDistintos
			}
		} else {
			//TODO lanzar excepcion NullPointerException
		}
		return valorReturn;
	}
	
	public class Metodo{
		
		private Method metodo;
		
		public Metodo(Method m){
			this.metodo = m;
		}
		
		public Method getMetodo(){
			return this.metodo;
		}
		
		public String getName(){
			return this.metodo.getName();
		}
		
		public String toString(){
			return this.metodo.getName();
		}

		public Class getTipoReturn() {
			return this.metodo.getReturnType();
		}
	}
	
public class Atributo{
		
		private Field atributo;
		
		public Atributo(Field f){
			this.atributo = f;
		}
		
		public Field getAtributo(){
			return this.atributo;
		}
		
		public String getName(){
			return this.atributo.getName();
		}
		
		public String toString(){
			return this.atributo.getName();
		}

		public Class getTipoReturn() {
			return this.atributo.getType();
		}
		
		public boolean equals(Object obj){	
			boolean salida = false;      
	        if (obj != null && (obj.getClass().equals(this.getClass()))) {
	           Atributo atributo = (Atributo)obj;           
	           salida  = (this.atributo.equals(atributo.getAtributo()));
	       }
	       return salida;
		}
	}
}
